/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.ai;

import com.serponix.game.Direction;
import com.serponix.game.GameModel;
import com.serponix.game.Player;
import com.serponix.game.Snake;
import com.serponix.game.food.Cake;
import com.serponix.game.food.Cherry;
import com.serponix.game.food.Food;
import com.serponix.game.food.TypPotraviny;
import com.serponix.game.objektyNaPlose.Clanek;
import com.serponix.game.objektyNaPlose.VecNaPolicku;
import com.serponix.game.objektyNaPlose.Zed;

/**
 * Artificial intelligence of a medium difficulty.
 *
 * @author Daniel Vala
 */
public class StandardAI extends AI {

	private enum Plan {

		JDU_ZA_JIDLEM, JDU_ZA_BONUSEM;
	}

	private final int sirkaPruhuJidla = 3;
	private Direction planovanySmer;
	private boolean odbocDobre;
	private int predchoziVelikostHada;
	private int rychlostZmensovani;
	private int jedDal;

	/**
	 * Přiřadí hráči umělou inteligenci.
	 *
	 * @param model  Model hry.
	 * @param player Hráč, který je ovládán umělou inteligencí.
	 */
	public StandardAI(GameModel model, Player player) {
		super(model, player);
		predchoziVelikostHada = snake.getDelka();
	}

	/**
	 * Ovládá daného hada.
	 * Tuto metodu je třeba volat při každém kroku hry.
	 */
	public void setNewDirection() {
		//        if (jedDal > 0) {
		//            jedDal--;
		//            hrac.changeSmer(had.getExistujiciSmer());
		//            return;
		//        }

		checkVelikostHada();

		// jestliže se nevyhýbám žádným hadům, jdu za jídlem
		if (!zkontrolujPredmety()) {
			chooseBetterFood();
		}
		predchoziVelikostHada = snake.getDelka();
	}

	/**
	 * Choose better food.
	 */
	private void chooseBetterFood() {
		// if I am too small or very close to win, I eat only food
		if (snake.getDelka() < CRITICAL_LENGTH || snake.getDelka() > snake.getVelikostProVitezstvi() - LOOK_AT_WIN_CELLS_REMAIN) {
			jdiZaPotravinou(TypPotraviny.JIDLO);
		} else { // else I eat what is close to me
			jdiZaPotravinou(getBlizsiPotravina());
		}
	}

	/**
	 * Check if snake avoided someone or something.
	 *
	 * @return True, if snake reacted on someone or something.
	 */
	private boolean zkontrolujPredmety() {
		return vyhniSeHadum() || vyhniSeStenam();
	}

	private void zkontrolujStreluNaZadku() {
	}

	private void checkVelikostHada() {
		// if length of snake was not decreased
		if ((predchoziVelikostHada <= snake.getDelka()) && (rychlostZmensovani > 0)) {
			rychlostZmensovani--;
		} // if length of snake was decreased
		else if (predchoziVelikostHada > snake.getDelka()) {
			rychlostZmensovani += 10;
		}

		// pokud se zmenšuji příliš rychle, zatočím a tím se vyhnu případným dalším střelám
		if (rychlostZmensovani >= 13) {
			Movement.turnRandomly(model, player);
			// jedDal = 5;
			rychlostZmensovani = 0;
		}
	}

	/**
	 * Pokusí se vyhnout překážce zatočením doleva, či doprava.
	 * Rozhodnutí kam zatočí je dáno náhodou.
	 * Pokud na stranu, kterou si had vybral, nemůže zatočit, zkusí ještě zatočit na stranu druhou.
	 */
	private void vyhniSePrekazce() {
		// nahoda, která rozhodne, zda had zatočí doprava, či doleva
		int nahoda = 1 + randomGenerator.nextInt(2);
		if (nahoda == 1) {
			boolean uspech = Movement.changeSmer(model, player, Direction.getSmerVlevo(snake.getExistingDirection()));
			// pokud se hadovi nepodaří zatočit doleva kvůli jiné překážce, zkusí zatočit doprava
			if (!uspech) {
				Movement.changeSmer(model, player, Direction.getSmerVpravo(snake.getExistingDirection()));
			}
		} else {
			// pokud se hadovi nepodaří zatočit doprava kvůli jiné překážce, zkusí zatočit doleva
			boolean uspech = Movement.changeSmer(model, player, Direction.getSmerVpravo(snake.getExistingDirection()));
			if (!uspech) {
				Movement.changeSmer(model, player, Direction.getSmerVlevo(snake.getExistingDirection()));
			}
		}
	}

	/**
	 * Zjistí, zda se v okolí hada nachází nepřátelský had.
	 * Pokud ano, snaží se mu různými způsoby vyhnout.
	 *
	 * @return True, pokud had nějáým způsobem na nepřátelského hada zareagoval nebo se pokusil zareagovat.
	 *         False, pokud ho ignoruje nebo se v okolí žádný nepřítel nenachází.
	 */
	private boolean vyhniSeHadum() {
		VecNaPolicku objektPredHadem = snake.getObjektPredHadem(5);
		if (objektPredHadem != null) {
			if (objektPredHadem instanceof Clanek) {
				Clanek clanek = (Clanek) objektPredHadem;
				Snake nepratelskyHad = clanek.getHad();
				if (nepratelskyHad.isVlastnostNeviditelnost() || nepratelskyHad.isVlastnostDuch()) {
					return false;
				} else if (!nepratelskyHad.isVlastnostOdolny()) {
					if (vystrelLepsiZbran()) {
						return true;
					}
					// pokud je had odolný, zkusí had střelit raketu (ne laser)
				} else {
					if (snake.strelRaketu()) {
						return true;
					}
				}
				// pokud jsem tank větší než 8 článků, nevyhnu se protivníkovi
				if (snake.isVlastnostTank() && snake.getDelka() > 8) {
					return false;
				}
				if (snake.isVlastnostTurtle()) {
					Movement.changeSmer(model, player, Direction.NOWHERE);
					return true;
				}
				// pokud nemůžu ani vystřelit, ani nejsem tank, ani nemůžu zastavit, hadovy se pokusím vyhnout
				vyhniSePrekazce();
				return true;
			}
		}
		return false;
	}

	public boolean vyhniSeStenam() {
		VecNaPolicku objektPredHadem = snake.getObjektPredHadem(1);
		if (objektPredHadem != null) {
			if (objektPredHadem instanceof Zed) {
				vyhniSePrekazce();
			}
			return true;
		}
		objektPredHadem = snake.getObjektPredHadem(2);
		if (objektPredHadem != null) {
			if (objektPredHadem instanceof Zed) {
				vyhniSePrekazce();
			}
			return true;
		}
		objektPredHadem = snake.getObjektPredHadem(3);
		if (objektPredHadem != null) {
			if (objektPredHadem instanceof Zed) {
				vyhniSePrekazce();
			}
			return true;
		}
		return false;
	}

	/**
	 * Vystřelí lepší zbraň.
	 * Pokud má raketu, vystřelí ji. Pokud ne, vystřelí laser.
	 *
	 * @return true, pokud vystřelil. Jinak false.
	 */
	private boolean vystrelLepsiZbran() {
		boolean vystrelil = false;
		if (snake.getPocetRaket() > 0) {
			vystrelil = snake.strelRaketu();
			if (!vystrelil) {
				vystrelil = snake.strelLaser();
			}
		} else {
			vystrelil = snake.strelLaser();
		}
		return vystrelil;
	}

	/**
	 * Had vystřelí s pravděpodobností 1:prpst laser nebo raketu.
	 *
	 * @return true, pokud vystřelil. Jinak false.
	 */
	private boolean nahodnaStrelba(int prpst) {
		int nahoda = 1 + randomGenerator.nextInt(prpst);
		if (nahoda == 1) {
			return snake.strelLaser();
		}
		if (nahoda == 2) {
			return snake.strelRaketu();
		}
		return false;
	}

	/**
	 * Vrátí true s pravděpodobností 1:prpst.
	 *
	 * @param prpst Čím vyšší, tím nepravděpodobněji metoda vrátí true.
	 * @return true při štestí, jinak false.
	 */
	protected boolean nahoda(int prpst) {
		int nahoda = 1 + randomGenerator.nextInt(prpst);
		if (nahoda == 1) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Had se otočí.
	 * Zvolí horní zatáčku, pokud je jídlo výš než on.
	 * Stejně tak pro všechny ostatní pozice.
	 */
	private void otocSe() {
		if (snake.getSmer() == Direction.LEFT) {
			// pokud je jídlo výš než já, otočim se horní zatáčkou
			if (snake.getY() > model.jidlo.getY()) {
				Movement.changeSmer(model, player, Direction.UP);
				// jinak dolní zatáčkou
			} else {
				Movement.changeSmer(model, player, Direction.DOWN);
			}
			planovanySmer = Direction.RIGHT;
		} else if (snake.getSmer() == Direction.RIGHT) {
			// pokud je jídlo výš než já, otočim se horní zatáčkou
			if (snake.getY() > model.jidlo.getY()) {
				Movement.changeSmer(model, player, Direction.UP);
				// jinak dolní zatáčkou
			} else {
				Movement.changeSmer(model, player, Direction.DOWN);
			}
			planovanySmer = Direction.LEFT;
		} else if (snake.getSmer() == Direction.UP) {
			// pokud je jídlo výš než já, otočim se levou zatáčkou
			if (snake.getX() > model.jidlo.getX()) {
				Movement.changeSmer(model, player, Direction.LEFT);
				// jinak pravou zatáčkou
			} else {
				Movement.changeSmer(model, player, Direction.RIGHT);
			}
			planovanySmer = Direction.DOWN;
		} else if (snake.getSmer() == Direction.DOWN) {
			// pokud je jídlo výš než já, otočim se levou zatáčkou
			if (snake.getX() > model.jidlo.getX()) {
				Movement.changeSmer(model, player, Direction.LEFT);
				// jinak pravou zatáčkou
			} else {
				Movement.changeSmer(model, player, Direction.RIGHT);
			}
			planovanySmer = Direction.UP;
		}
	}

	/**
	 * Had zkusí odbočit s pravděpodobností 1:3 do zadaného směru.
	 *
	 * @param smer Směr, kam se had má pokusit odbočit.
	 */
	private void zkusOdbocit(Direction smer) {
		if (!odbocDobre) {
			int nahoda = 1 + randomGenerator.nextInt(3);
			if (nahoda == 2) {
				Movement.changeSmer(model, player, smer);
			}
		} else {
			Movement.changeSmer(model, player, smer);
			odbocDobre = false;
		}
	}

	/**
	 * Had zatočí nahoru, pokud je jídlo výš než než on. Jinak zatočí dolu.
	 */
	private void korigujVyskuPodlePotraviny(Food food) {
		// pokud je jídlo výš než já, zatočim nahoru
		if (snake.getY() > food.getY()) {
			Movement.changeSmer(model, player, Direction.UP);
		} // jinak dolu
		else {
			Movement.changeSmer(model, player, Direction.DOWN);
		}
	}

	/**
	 * Had zatočí vlevo, pokud je jídlo nalevo od něj. Jinak zatočí vpravo.
	 */
	private void korigujSirkuPodlePotraviny(Food food) {
		// pokud je jídlo výš než já, zatočim vlevo
		if (snake.getX() > food.getX()) {
			Movement.changeSmer(model, player, Direction.LEFT);
			// jinak vpravo
		} else {
			Movement.changeSmer(model, player, Direction.RIGHT);
		}
	}

	/**
	 * Vrátí typ potraviny, ke které je had blíž.
	 * Pokud je ke všem typům potravin stejně daleko, vrátí JIDLO.
	 * Pokud bonus neexistuje, vrátí jídlo.
	 *
	 * @return TypPotraviny, ke které je had nejblíže.
	 */
	private TypPotraviny getBlizsiPotravina() {
		Cake jidlo = model.getJidlo();
		Cherry bonus = model.getBonus();

		if (bonus == null) {
			return TypPotraviny.JIDLO;
		}

		int vzdalenostJidlaX = Math.abs(snake.getX() - jidlo.getX());
		int vzdalenostJidlaY = Math.abs(snake.getY() - jidlo.getY());
		int vzdalenostHadaOdJidla = vzdalenostJidlaX + vzdalenostJidlaY;

		int vzdalenostBonusuaX = Math.abs(snake.getX() - bonus.getX());
		int vzdalenostBonusuY = Math.abs(snake.getY() - bonus.getY());
		int vzdalenostHadaOdBonusu = vzdalenostBonusuaX + vzdalenostBonusuY;

		if (vzdalenostHadaOdJidla <= vzdalenostHadaOdBonusu) {
			return TypPotraviny.JIDLO;
		} else {
			return TypPotraviny.BONUS;
		}
	}

	private VecNaPolicku getVecNadHadem() {
		return model.getObjektNaHerniPlose(snake.getX(), snake.getY() - 1);
	}

	private VecNaPolicku getVecPodHadem() {
		return model.getObjektNaHerniPlose(snake.getX(), snake.getY() + 1);
	}

	private VecNaPolicku getVecVlevoOdHada() {
		return model.getObjektNaHerniPlose(snake.getX() - 1, snake.getY());
	}

	private VecNaPolicku getVecVpravoOdHada() {
		return model.getObjektNaHerniPlose(snake.getX() + 1, snake.getY());
	}

	/**
	 * Had nastaví svůj směr tak, aby co nejdříve sebral danou potravinu.
	 * Jako človék se ale nemusí "trefit" a pojede těsně vedle potraviny.
	 * Pokud daná potravina na herní ploše není, nic se neprovede.
	 */
	private void jdiZaPotravinou(TypPotraviny typPotraviny) {
		if (snake.getSmer() == Direction.NOWHERE) {
			Movement.changeSmer(model, player, snake.getExistingDirection());
			return;
		}

		Food potravina = model.getPotravina(typPotraviny);
		if (potravina == null) {
			return;
		}

		int potravinaX = potravina.getX();
		int potravinaY = potravina.getY();

		// vzdálenosti hada od jídla
		int vzdalenostX = Math.abs(snake.getX() - potravina.getX());
		int vzdalenostY = Math.abs(snake.getY() - potravina.getY());

		if (snake.isVlastnostSvoboda()) {
			int svobodnaVzdalenostX = 0;
			if (snake.getX() > potravinaX) {
				svobodnaVzdalenostX = (model.getSirkaLogicka() - snake.getX()) + potravinaX;
			} else if (snake.getX() < potravinaX) {
				svobodnaVzdalenostX = (model.getSirkaLogicka() - potravinaX) + snake.getX();
			}

			if (svobodnaVzdalenostX < vzdalenostX) {
				potravinaX = model.getSirkaLogicka() + 1;
			}

			int svobodnaVzdalenostY = 0;
			if (snake.getY() > potravinaY) {
				svobodnaVzdalenostY = (model.getVyskaLogicka() - snake.getY()) + potravinaY;
			} else if (snake.getY() < potravinaY) {
				svobodnaVzdalenostY = (model.getVyskaLogicka() - potravinaY) + snake.getY();
			}

			if (svobodnaVzdalenostY < vzdalenostY) {
				potravinaY = model.getVyskaLogicka() + 1;
			}
		}

		// pokud nejsem v žádném pruhu jídla
		if (vzdalenostY >= 3 && vzdalenostX >= 3) {
			// pokud jsem v zóně nalevo nahoře od jídla
			if (snake.getY() < potravinaY && snake.getX() < potravinaX) {
				if (snake.getSmer() == Direction.LEFT) {
					Movement.changeSmer(model, player, Direction.DOWN);
				} else if (snake.getSmer() == Direction.UP) {
					Movement.changeSmer(model, player, Direction.RIGHT);
				}
			} // pokud jsem v zóně napravo nahoře od jídla
			else if (snake.getY() < potravinaY && snake.getX() > potravinaX) {
				if (snake.getSmer() == Direction.RIGHT) {
					Movement.changeSmer(model, player, Direction.DOWN);
				} else if (snake.getSmer() == Direction.UP) {
					Movement.changeSmer(model, player, Direction.LEFT);
				}
			} // pokud jsem v zóně nalevo dole od jídla
			else if (snake.getY() > potravinaY && snake.getX() < potravinaX) {
				if (snake.getSmer() == Direction.DOWN) {
					Movement.changeSmer(model, player, Direction.RIGHT);
				} else if (snake.getSmer() == Direction.LEFT) {
					Movement.changeSmer(model, player, Direction.UP);
				}
			} // pokud jsem v zóně napravo dole od jídla
			else if (snake.getY() > potravinaY && snake.getX() > potravinaX) {
				if (snake.getSmer() == Direction.DOWN) {
					Movement.changeSmer(model, player, Direction.LEFT);
				} else if (snake.getSmer() == Direction.RIGHT) {
					Movement.changeSmer(model, player, Direction.UP);
				}
			}
		}

		////////////////  POKUD JSEM V PRUHU JÍDLA ////////////////////

		// jestliže jsem blízko jídla v obou dimenzích, nedělám nic
		if (vzdalenostY < 3 && vzdalenostX < 3) {
			return;
		} // pokud jsem nalevo od jídla
		else if (vzdalenostY < 3 && snake.getX() < potravinaX) {
			if (snake.getSmer() == Direction.DOWN || snake.getSmer() == Direction.UP) {
				zkusOdbocit(Direction.RIGHT);
			} else if (snake.getSmer() == Direction.LEFT) {
				korigujVyskuPodlePotraviny(potravina);
			} // zkorigování směru
			else if (snake.getSmer() == Direction.RIGHT) {
				// pokud had nejede přímo na potravinu
				if (vzdalenostY != 0) {
					if (nahoda(10)) {
						korigujVyskuPodlePotraviny(potravina);
						odbocDobre = true;
					}
				}
			}
		} // pokud jsem napravo od jídla
		else if (vzdalenostY < 3 && snake.getX() > potravinaX) {
			if (snake.getSmer() == Direction.DOWN || snake.getSmer() == Direction.UP) {
				zkusOdbocit(Direction.LEFT);
			} else if (snake.getSmer() == Direction.RIGHT) {
				korigujVyskuPodlePotraviny(potravina);
			} // zkorigování směru
			else if (snake.getSmer() == Direction.LEFT) {
				// pokud had nejede přímo na potravinu
				if (vzdalenostY != 0) {
					if (nahoda(10)) {
						korigujVyskuPodlePotraviny(potravina);
						odbocDobre = true;
					}
				}
			}
		} // pokud jsem nahoře nad jídlem
		else if (vzdalenostX < 3 && snake.getY() < potravinaY) {
			if (snake.getSmer() == Direction.RIGHT || snake.getSmer() == Direction.LEFT) {
				zkusOdbocit(Direction.DOWN);
			} else if (snake.getSmer() == Direction.UP) {
				korigujSirkuPodlePotraviny(potravina);
			} // zkorigování směru
			else if (snake.getSmer() == Direction.DOWN) {
				// pokud had nejede přímo na potravinu
				if (vzdalenostX != 0) {
					if (nahoda(10)) {
						korigujSirkuPodlePotraviny(potravina);
						odbocDobre = true;
					}
				}
			}
		} // pokud jsem dole pod jídlem
		else if (vzdalenostX < 3 && snake.getY() > potravinaY) {
			if (snake.getSmer() == Direction.RIGHT || snake.getSmer() == Direction.LEFT) {
				zkusOdbocit(Direction.UP);
			} else if (snake.getSmer() == Direction.DOWN) {
				korigujSirkuPodlePotraviny(potravina);
			}// zkorigování směru
			else if (snake.getSmer() == Direction.UP) {
				// pokud had nejede přímo na potravinu
				if (vzdalenostX != 0) {
					if (nahoda(10)) {
						korigujSirkuPodlePotraviny(potravina);
						odbocDobre = true;
					}
				}
			}
		}
	}
}